// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
// SPDX-License-Identifier: MIT

package metrics

import (
	"errors"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)

const (
	// The system clock is not synchronized to a reliable
	// server (TIME_ERROR).
	timeError = 5
	// The timex.Status time resolution bit (STA_NANO),
	// 0 = microsecond, 1 = nanoseconds.
	staNano = 0x2000

	// 1 second in
	nanoSeconds  = 1000000000
	microSeconds = 1000000
)

type TimeXCollector struct {
	metrics map[string]*MetricDesc
}

func NewTimeXCollector() *TimeXCollector {
	const subsystem = "timex"

	return &TimeXCollector{
		metrics: map[string]*MetricDesc{
			"offset": {
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, subsystem, "offset_seconds"),
					"Time offset in between local system and reference clock",
					nil,
					nil,
				),
				Type: prometheus.GaugeValue,
			},
			"freq": {
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, subsystem, "frequency_adjustment_ratio"),
					"Frequency adjustment in between local system and reference clock",
					nil,
					nil,
				),
				Type: prometheus.GaugeValue,
			},
			"maxerror": {
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, subsystem, "max_error_seconds"),
					"Maximum error in between local system and reference clock",
					nil,
					nil,
				),
				Type: prometheus.GaugeValue,
			},
			"esterror": {
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, subsystem, "estimated_error_seconds"),
					"Estimated error in between local system and reference clock",
					nil,
					nil,
				),
				Type: prometheus.GaugeValue,
			},
			"status": {
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, subsystem, "status"),
					"Value of the status array bits",
					nil,
					nil,
				),
				Type: prometheus.GaugeValue,
			},
			"constant": {
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, subsystem, "loop_time_constant"),
					"Phase-locked loop time constant",
					nil,
					nil,
				),
				Type: prometheus.GaugeValue,
			},
			"tick": {
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, subsystem, "tick_seconds"),
					"Seconds between clock ticks",
					nil,
					nil,
				),
				Type: prometheus.GaugeValue,
			},
			"syncstatus": {
				Desc: prometheus.NewDesc(
					prometheus.BuildFQName(namespace, subsystem, "sync_status"),
					"Is clock synchronized to a reliable server (1 = yes, 0 = no)",
					nil,
					nil,
				),
				Type: prometheus.GaugeValue,
			},
		},
	}
}

func (c *TimeXCollector) Update(ch chan<- prometheus.Metric) error {
	var timex unix.Timex

	status, err := unix.ClockAdjtime(unix.CLOCK_REALTIME, &timex)
	if err != nil {
		logrus.Errorf("Failed to call clock_adjtime: %v", err)
		return errors.New("failed to call clock_adjtime")
	}

	// Convert offset from microseconds/nanoseconds to seconds
	var offset float64
	if status&staNano != 0 {
		offset = float64(timex.Offset) / nanoSeconds
	} else {
		offset = float64(timex.Offset) / microSeconds
	}

	ch <- prometheus.MustNewConstMetric(
		c.metrics["offset"].Desc,
		c.metrics["offset"].Type,
		offset,
	)

	ch <- prometheus.MustNewConstMetric(
		c.metrics["freq"].Desc,
		c.metrics["freq"].Type,
		float64(timex.Freq)/float64(1<<16),
	)

	ch <- prometheus.MustNewConstMetric(
		c.metrics["maxerror"].Desc,
		c.metrics["maxerror"].Type,
		float64(timex.Maxerror)/microSeconds,
	)

	ch <- prometheus.MustNewConstMetric(
		c.metrics["esterror"].Desc,
		c.metrics["esterror"].Type,
		float64(timex.Esterror)/microSeconds,
	)

	ch <- prometheus.MustNewConstMetric(
		c.metrics["status"].Desc,
		c.metrics["status"].Type,
		float64(status),
	)

	ch <- prometheus.MustNewConstMetric(
		c.metrics["constant"].Desc,
		c.metrics["constant"].Type,
		float64(timex.Constant),
	)

	ch <- prometheus.MustNewConstMetric(
		c.metrics["tick"].Desc,
		c.metrics["tick"].Type,
		float64(timex.Tick)/microSeconds,
	)

	// Sync status
	var syncStatus float64
	if status != timeError {
		syncStatus = 1
	}
	ch <- prometheus.MustNewConstMetric(
		c.metrics["syncstatus"].Desc,
		c.metrics["syncstatus"].Type,
		syncStatus,
	)

	return nil
}

// Describe sends the descriptors of each metric over the provided channel.
// This implements prometheus.Collector.
func (c *TimeXCollector) Describe(ch chan<- *prometheus.Desc) {
	for _, m := range c.metrics {
		ch <- m.Desc
	}
}

// Collect collects the metrics by calling Update and forwarding metrics to the channel.
// This implements prometheus.Collector.
func (c *TimeXCollector) Collect(ch chan<- prometheus.Metric) {
	// Ignore errors during collection; log and continue.
	if err := c.Update(ch); err != nil {
		logrus.WithField("err", err).Warn("TimeXCollector update failed during Collect")
	}
}
